package com.starsea.book.ui.main

import android.app.Application
import android.text.TextUtils
import androidx.collection.arrayMapOf
import androidx.fragment.app.Fragment
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.google.gson.Gson
import com.google.gson.JsonParser
import com.starsea.book.base.BaseViewModel
import com.starsea.book.constant.AppConst
import com.starsea.book.constant.AppLog
import com.starsea.book.constant.BookType
import com.starsea.book.constant.EventBus
import com.starsea.book.data.appDb
import com.starsea.book.data.entities.Book
import com.starsea.book.data.entities.BookSource
import com.starsea.book.help.AppWebDav
import com.starsea.book.help.DefaultData
import com.starsea.book.help.book.BookHelp
import com.starsea.book.help.book.isLocal
import com.starsea.book.help.book.isSameNameAuthor
import com.starsea.book.help.book.isUpError
import com.starsea.book.help.book.removeType
import com.starsea.book.help.book.sync
import com.starsea.book.help.config.AppConfig
import com.starsea.book.help.http.newCallResponseBody
import com.starsea.book.help.http.okHttpClient
import com.starsea.book.help.http.postJson
import com.starsea.book.help.http.text
import com.starsea.book.model.CacheBook
import com.starsea.book.model.ReadBook
import com.starsea.book.model.localBook.LocalBook
import com.starsea.book.model.webBook.WebBook
import com.starsea.book.service.CacheBookService
import com.starsea.book.ui.main.bookshelf.style1.books.BooksFragment
import com.starsea.book.ui.newpage.CategoryFragment
import com.starsea.book.ui.newpage.HomeFragment
import com.starsea.book.ui.newpage.MineFragment
import com.starsea.book.ui.newpage.WelfareFragment
import com.starsea.book.ui.newpage.entity.BaseEntity
import com.starsea.book.ui.newpage.entity.BookInfoEntity
import com.starsea.book.ui.newpage.entity.BookShelfEntity
import com.starsea.book.ui.newpage.entity.BookShelfParentEntity
import com.starsea.book.ui.newpage.utils.gsonList
import com.starsea.book.ui.newpage.utils.gsonNormal
import com.starsea.book.ui.newpage.viewmodel.BookShelfDataState
import com.starsea.book.utils.LogUtils
import com.starsea.book.utils.postEvent
import com.starsea.book.utils.toastOnUi
import kotlinx.coroutines.Job
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import okhttp3.RequestBody.Companion.toRequestBody
import java.util.concurrent.CopyOnWriteArraySet
import java.util.concurrent.Executors
import kotlin.math.min

class MainViewModel(application: Application) : BaseViewModel(application) {
    private var threadCount = AppConfig.threadCount
    private var upTocPool =
        Executors.newFixedThreadPool(min(threadCount, AppConst.MAX_THREAD)).asCoroutineDispatcher()
    private val waitUpTocBooks = arrayListOf<String>()
    private val onUpTocBooks = CopyOnWriteArraySet<String>()
    val onUpBooksLiveData = MutableLiveData<Int>()
    private var upTocJob: Job? = null
    private var cacheBookJob: Job? = null


    fun getFragmentList(): MutableList<Fragment> {
        val list = arrayListOf<Fragment>()
        list.add(BooksFragment())
        list.add(HomeFragment())
        list.add(WelfareFragment())
        list.add(CategoryFragment())
        list.add(MineFragment())
        return list
    }

    override fun onCleared() {
        super.onCleared()
        upTocPool.close()
    }

    fun upPool() {
        threadCount = AppConfig.threadCount
        upTocPool.close()
        upTocPool = Executors
            .newFixedThreadPool(min(threadCount, AppConst.MAX_THREAD)).asCoroutineDispatcher()
    }

    fun isUpdate(bookUrl: String): Boolean {
        return onUpTocBooks.contains(bookUrl)
    }

    fun upAllBookToc() {
        execute {
            addToWaitUp(appDb.bookDao.hasUpdateBooks)
        }
    }

    fun upToc(books: List<Book>) {
        execute(context = upTocPool) {
            books.filter {
                !it.isLocal && it.canUpdate
            }.let {
                addToWaitUp(it)
            }
        }
    }

    @Synchronized
    private fun addToWaitUp(books: List<Book>) {
        books.forEach { book ->
            if (!waitUpTocBooks.contains(book.bookUrl) && !onUpTocBooks.contains(book.bookUrl)) {
                waitUpTocBooks.add(book.bookUrl)
            }
        }
        if (upTocJob == null) {
            startUpTocJob()
        }
    }

    private fun startUpTocJob() {
        postUpBooksLiveData()
        upTocJob = viewModelScope.launch(upTocPool) {
            while (isActive) {
                when {
                    waitUpTocBooks.isEmpty() -> {
                        upTocJob?.cancel()
                        upTocJob = null
                    }

                    onUpTocBooks.size < threadCount -> {
                        updateToc()
                    }

                    else -> {
                        delay(500)
                    }
                }
            }
        }
    }

    /**
     * 更新书架的内容列表
     */
    @Synchronized
    private fun updateToc() {
        val bookUrl = waitUpTocBooks.firstOrNull() ?: return
        if (onUpTocBooks.contains(bookUrl)) {
            waitUpTocBooks.remove(bookUrl)
            postUpBooksLiveData()
            return
        }
        val book = appDb.bookDao.getBook(bookUrl)
        if (book == null) {
            waitUpTocBooks.remove(bookUrl)
            postUpBooksLiveData()
            return
        }
        // 获取书源
        val source = appDb.bookSourceDao.getBookSource(book.origin)
        book.removeType(BookType.updateError)
        if (source == null) {
            if (!book.isUpError) {
//                book.addType(BookType.updateError)
                appDb.bookDao.update(book)
            }
            waitUpTocBooks.remove(book.bookUrl)
            postUpBooksLiveData()
            return
        }

        waitUpTocBooks.remove(bookUrl)
        upTocAdd(bookUrl)
        execute(context = upTocPool, executeContext = upTocPool) {
            kotlin.runCatching {
                val oldBook = book.copy()
                WebBook.runPreUpdateJs(source, book)
                if (book.tocUrl.isBlank()) {
                    WebBook.getBookInfoAwait(source, book)
                }
                val toc = WebBook.getChapterListAwait(source, book).getOrThrow()
                book.sync(oldBook)
                book.removeType(BookType.updateError)
                if (book.bookUrl == bookUrl) {
                    appDb.bookDao.update(book)
                } else {
                    upTocAdd(book.bookUrl)
                    appDb.bookDao.insert(book)
                    BookHelp.updateCacheFolder(oldBook, book)
                }
                appDb.bookChapterDao.delByBook(bookUrl)
                appDb.bookChapterDao.insert(*toc.toTypedArray())
                if (book.isSameNameAuthor(ReadBook.book)) {
                    ReadBook.book = book
                    ReadBook.chapterSize = book.totalChapterNum
                }
                addDownload(source, book)
            }.onFailure {
//                AppLog.put("${book.name} 更新目录失败\n${it.localizedMessage}", it)
//                //这里可能因为时间太长书籍信息已经更改,所以重新获取
//                appDb.bookDao.getBook(book.bookUrl)?.let { book ->
//                    book.addType(BookType.updateError)
//                    appDb.bookDao.update(book)
//                }
            }
        }.onCancel {
            upTocCancel(bookUrl)
            upTocCancel(book.bookUrl)
        }.onFinally {
            upTocFinally(bookUrl)
            upTocFinally(book.bookUrl)
            postUpBooksLiveData()
        }
    }

    fun postUpBooksLiveData(reset: Boolean = false) {
        if (AppConfig.showWaitUpCount) {
            onUpBooksLiveData.postValue(waitUpTocBooks.size + onUpTocBooks.size)
        } else if (reset) {
            onUpBooksLiveData.postValue(0)
        }
    }

    @Synchronized
    private fun upTocAdd(bookUrl: String) {
        onUpTocBooks.add(bookUrl)
        postEvent(EventBus.UP_BOOKSHELF, bookUrl)
    }

    @Synchronized
    private fun upTocCancel(bookUrl: String) {
        onUpTocBooks.remove(bookUrl)
        waitUpTocBooks.add(bookUrl)
        postEvent(EventBus.UP_BOOKSHELF, bookUrl)
    }

    @Synchronized
    private fun upTocFinally(bookUrl: String) {
        waitUpTocBooks.remove(bookUrl)
        onUpTocBooks.remove(bookUrl)
        postEvent(EventBus.UP_BOOKSHELF, bookUrl)
        if (waitUpTocBooks.isEmpty()
            && onUpTocBooks.isEmpty()
            && cacheBookJob == null
            && !CacheBookService.isRun
        ) {
            //所有目录更新完再开始缓存章节
            cacheBook()
        }
    }

    @Synchronized
    private fun addDownload(source: BookSource, book: Book) {
        if (AppConfig.preDownloadNum == 0) return
        val endIndex = min(
            book.totalChapterNum - 1,
            book.durChapterIndex.plus(AppConfig.preDownloadNum)
        )
        val cacheBook = CacheBook.getOrCreate(source, book)
        cacheBook.addDownload(book.durChapterIndex, endIndex)
    }

    /**
     * 缓存书籍
     */
    private fun cacheBook() {
        cacheBookJob?.cancel()
        cacheBookJob = viewModelScope.launch(upTocPool) {
            while (isActive) {
                if (CacheBookService.isRun || !CacheBook.isRun) {
                    cacheBookJob?.cancel()
                    cacheBookJob = null
                    return@launch
                }
                CacheBook.cacheBookMap.forEach {
                    val cacheBookModel = it.value
                    while (cacheBookModel.waitCount > 0) {
                        //有目录更新是不缓存,优先更新目录,现在更多网站限制并发
                        if (waitUpTocBooks.isEmpty()
                            && onUpTocBooks.isEmpty()
                            && CacheBook.onDownloadCount < threadCount
                        ) {
                            cacheBookModel.download(this, upTocPool)
                        } else {
                            delay(100)
                        }
                    }
                }
            }
        }
    }

    fun postLoad() {
        execute {
            if (appDb.httpTTSDao.count == 0) {
                DefaultData.httpTTS.let {
                    appDb.httpTTSDao.insert(*it.toTypedArray())
                }
            }
        }
    }

    fun restoreWebDav(name: String) {
        execute {
            AppWebDav.restoreWebDav(name)
        }
    }

    val netBookselfResult = MutableLiveData<BaseEntity<MutableList<BookShelfEntity>>>()

    val _bookShelfDataState = MutableLiveData<BookShelfDataState>()
    val bookShelfDataState: LiveData<BookShelfDataState> get() = _bookShelfDataState

    fun getNetBookShelf(local: MutableList<BookShelfEntity>) {

        execute {
            okHttpClient.newCallResponseBody {
                url(AppConst.BASE_URL.plus("front-api/book-shelf/favorBookList?favorTagId="))
                    .post("".toRequestBody())
            }.text().let {
                LogUtils.e(">>>> ", "net Data222 2  = >>.${it}")
                val entity: BaseEntity<MutableList<BookShelfParentEntity>> =
                    it.gsonList(BookShelfParentEntity::class.java)
//                netBookselfResult.postValue(entity)
                _bookShelfDataState.postValue(BookShelfDataState.Success(entity, local))
            }
        }.onError {
            context.toastOnUi(it.localizedMessage ?: "ERROR")
        }

    }

//    val bookselfListResult = MutableLiveData<BaseEntity<MutableList<BookShelfEntity>>>()

//    fun bookselfList(
//    ) {
//        execute {
//            okHttpClient.newCallResponseBody {
//                url(AppConst.BASE_URL.plus("api/business-service/front-api/bookShelf/favorBookList"))
//                    .post("".toRequestBody())
//            }.text().let {
//                LogUtils.e(">>>> ", "bookselfList  = >>.${it}")
//                val entity: BaseEntity<MutableList<BookShelfEntity>> =
//                    it.gsonList(BookShelfEntity::class.java)
//                bookselfListResult.postValue(entity)
//
//            }
//        }.onError {
//            context.toastOnUi(it.localizedMessage ?: "ERROR")
//        }
//
//    }


    val deleteBookselfResult = MutableLiveData<BaseEntity<String?>>()

    fun deleteNetBookShelf(shelfIds: String) {

        execute {
            okHttpClient.newCallResponseBody {
                url(AppConst.BASE_URL.plus("front-api/book-shelf/deleteFavorBook?shelfIds=${shelfIds}"))
                    .post("".toRequestBody())
            }.text().let {
                LogUtils.e(">>>> ", "net Data222 2  = >>.${it}")

                val baseEntity = BaseEntity<String?>()

                val objects = JsonParser.parseString(it).asJsonObject
                baseEntity.status = objects.get("status").asInt
                baseEntity.msg = objects.get("msg").asString
                delay(1500)
                deleteBookselfResult.postValue(baseEntity)

            }
        }.onError {
            context.toastOnUi(it.localizedMessage ?: "ERROR")
        }

    }

    fun delLocalBook(book: Book, deleteOriginal: Boolean = false, success: (() -> Unit)? = null) {
        execute {
            book.let {
                it.delete()
                if (it.isLocal) {
                    LocalBook.deleteBook(it, deleteOriginal)
                }
            }
        }.onSuccess {
            success?.invoke()
        }
    }


    val bookData = MutableLiveData<Book>()

    val bookInfoResult = MutableLiveData<BaseEntity<BookInfoEntity>>()

    fun getBookInfo(bookId: String) {

        val book = appDb.bookDao.getBook(bookId)

        if (book != null) {
            bookData.postValue(book!!)
//            bookInfoResult.postValue(entity)

            return
        }
        execute {

            okHttpClient.newCallResponseBody {
                val map: MutableMap<String, String> = arrayMapOf()
                map["bookId"] = bookId
                url(AppConst.BASE_URL.plus("front-api/book/book_read_detail?bookId=${bookId}"))
                    .post("".toRequestBody())
            }.text().let {

                LogUtils.e(">>>>> ", it)
                val entity: BaseEntity<BookInfoEntity> =
                    it.gsonNormal(BookInfoEntity::class.java)

                if (entity.status != 200) {
                    bookInfoResult.postValue(entity)
                } else {
                    val book = Book(
                        name = entity.data!!.bookTitle.toString(),
                        author = entity.data!!.authorName.toString(),
                        kind = "",
                        bookUrl = entity.data!!.bookId.toString(),
                        origin = "",
                        originName = "",
                        type = 1,
                        wordCount = entity.data!!.wordCount.toString(),
                        latestChapterTitle = "",
                        coverUrl = entity.data!!.bookCover.toString(),
                        intro = entity.data!!.bookDesc.toString(),
                        tocUrl = "",
                        originOrder = 0,
                        variable = ""
                    )
                    bookData.postValue(book)
                    bookInfoResult.postValue(entity)
                }
            }


        }.onError {
            AppLog.put(it.localizedMessage, it)
            context.toastOnUi(it.localizedMessage)
        }
    }

    fun saveBook(book: Book?, success: (() -> Unit)? = null) {

        book ?: return

        execute {
            if (book.order == 0) {
                book.order = appDb.bookDao.minOrder - 1
            }
            appDb.bookDao.getBook(book.name, book.author)?.let {
                book.durChapterPos = it.durChapterPos
                book.durChapterTitle = it.durChapterTitle
            }
            book.save()
            if (ReadBook.book?.name == book.name && ReadBook.book?.author == book.author) {
                ReadBook.book = book
            }
            LogUtils.e(">>>> ", " book.save()")
        }.onSuccess {

            LogUtils.e(">>>> ", "onSuccess")
            success?.invoke()
        }
    }

}